infoset is package{
  private import maybe;

  -- implement an infoset type
  -- intended to represent flexible datasets such as those found in JSON and XML

  type infoset is 
      iFalse or
      iTrue or
      iNull or
      iColl(hash of (string,infoset)) or
      iSeq(list of infoset) or
      iText(string) or
      iNum(long) or
      iFlt(float);

  type infoPathKey is kString(string) or kInt(integer);
  type infoPath is alias of list of infoPathKey;

  implementation indexable over infoset determines (infoPath,infoset) is {
    _index(I,K) is _perform(find(K,I),razer);
    _set_indexed(I,K,V) is updte(K,I,V);
    _delete_indexed(I,K) is remve(K,I);
    _index_member(V) from (I,K) where find(K,I) matches possible(V);
  } using {
    find has type (infoPath,infoset)=>maybe of infoset;
    find(list of {},I) is possible(I);
    find(list of {Ky;..Keys},I) is 
	case Ky in {
	  kString(K) where I matches iColl(M) and M[K] matches S is find(Keys,S);
	  kInt(Ix) where I matches iSeq(L) and L[Ix] matches S is find(Keys,S);
	  _ default is impossible(exception("index","$Ky not found" cast any,noWhere))
	};

    updte has type (infoPath,infoset,infoset)=>infoset;
    updte(list of {},I,_) is raise "path not valid";
    updte(list of {Ky},I,V) is case Ky in {
      kString(K) where I matches iColl(M) is iColl(_set_indexed(M,K,V));
      kInt(Ix) where I matches iSeq(L) is iSeq(_set_indexed(L,Ix,V));
      _ default is raise "illegal key $Ky"
    };
    updte(list of {Ky;..Keys},I,V) is case Ky in {
      kString(K) where I matches iColl(M) is iColl(_set_indexed(M,K,updte(Keys,M[K],V)));
      kInt(Ix) where I matches iSeq(L) is iSeq(_set_indexed(L,Ix,updte(Keys,L[Ix],V)));
      _ default is raise "illegal key $Ky";
    }

    remve has type (infoPath,infoset)=>infoset;
    remve(list of {},I) is I;
    remve(list of {Ky},I) is case Ky in {
      kString(K) where I matches iColl(M) is iColl(_delete_indexed(M,K));
      kInt(Ix) where I matches iSeq(L) is iSeq(_delete_indexed(L,Ix));
      _ default is raise "illegal key $Ky";
    }
    remve(list of {Ky;..Keys},I) is case Ky in {
      kString(K) where I matches iColl(M) is iColl(_set_indexed(M,K,remve(Keys,M[K])));
      kInt(Ix) where I matches iSeq(L) is iSeq(_set_indexed(L,Ix,remve(Keys,L[Ix])));
      _ default is raise "illegal key $Ky";
    }
  }

  implementation iterable over infoset determines infoset is {
    _iterate(I,F,S) is infoIterate(I,F,S);
  } using {
    infoIterate(I,F,S) is let{
      IterateInfo(_,NoMore(X)) is NoMore(X);
      IterateInfo(iColl(M),St) is _iterate(M,IterateInfo,St);
      IterateInfo(iSeq(L),St) is _iterate(L,IterateInfo,St);
      IterateInfo(Info,St) default is F(Info,St);
    } in IterateInfo(I,S);
  }

  implementation indexed_iterable over infoset determines (infoPath,infoset) is {
    _ixiterate(I,F,St) is indexInfoIterate(I,F,St,list of {});
  } using {
    indexInfoIterate(_,_,NoMore(X),_) is NoMore(X);
    indexInfoIterate(iColl(M),F,S,P) is _ixiterate(M,pathFun,S) using {
      pathFun(Ky,El,St) is indexInfoIterate(El,F,St,list of {P..;kString(Ky)});
    };
    indexInfoIterate(iSeq(L),F,S,P) is _ixiterate(L,pathFun,S) using {
      pathFun(Ix,El,St) is indexInfoIterate(El,F,St,list of {P..;kInt(Ix)});
    }
    indexInfoIterate(I,F,St,P) default is F(P,I,St);
  };

  implementation pPrint over infoset is {
    ppDisp(iColl(M)) is ppSequence(2, cons of {ppStr("{"); ppSequence(0,dispContent(M)); ppStr("}")});
    ppDisp(iSeq(L)) is ppSequence(0, cons of {ppStr("["); ppSequence(0,dispSeq(L)); ppStr("]")});
    ppDisp(iText(S)) is ppStr(display(S));
    ppDisp(iNum(I)) is ppStr(display(I));
    ppDisp(iFlt(F)) is ppStr(display(F));
    ppDisp(iFalse) is ppStr("false");
    ppDisp(iTrue) is ppStr("true");
    ppDisp(iNull) is ppStr("null");

    private
    dispContent(M) is interleave(cons of { ppSequence(0,cons of {ppStr(display(K));ppStr(":");ppDisp(V)}) where K->V in M },ppStr(","));
    
    private
    dispSeq(L) is interleave(cons of { ppDisp(E) where E in L},ppStr(","));

    private
    interleave(nil,_) is nil;
    interleave(L matching cons(H,nil),_) is L;
    interleave(cons(E1,L),S) is cons(E1,cons(S,interleave(L,S)));
  }

  jParse(list of {'t';'r';'u';'e';..L}) is (iTrue,L);
  jParse(list of {'f';'a';'l';'s';'e';..L}) is (iFalse,L);
  jParse(list of {'n';'u';'l';'l';..L}) is (iNull,L);
  jParse(L matching (list of {'-';.._})) is parseNumber(L);
  jParse(L matching (list of {D;.._})) where isDigit(D) is parseNumber(L);

  parseNumber(Str) is let{
    parseInt(list of {D;..L},Nm) where isDigit(D) is parseInt(L,Nm*10l+digitVal(D));
    parseInt(L,Nm) default is (Nm,L);

    parseFrac(list of {D;..L},Nm,F) where isDigit(D) is 
	parseFrac(L,Nm+(digitVal(D)as float)*F,F/10.0);
    parseFrac(L,Nm,_) default is (Nm,L);

    parseNum(list of {'-';..L}) is valof{
      (I,Rest) is parseNum(L);
      valis (negate(I),Rest)
    }
    parseNum(L) is parseMore@parseInt(L,0l);

    parseMore(Nm,list of {'.';..L}) is 
	parseExp@parseFrac(L,Nm as float,0.1);
    parseMore(Nm,L matching (list of {'e';.._})) is
	parseExp(Nm as float,L);
    parseMore(Nm,L) is (iNum(Nm),L);

    parseExp(Nm,list of {E;..L}) where E='e' or E='E' is parseX(Nm,L);
    parseExp(Nm,L) is (iFlt(Nm),L);

    parseX(Nm,list of {'+';..L}) is parseX(Nm,L);
    parseX(Nm,L) is valof{
      (Ex,LL) is parseInt(L,0l);
      valis (iFlt(Nm*10.0**(Ex as float)),LL)
    }

    negate(iNum(I)) is iNum(-I);
    negate(iFlt(F)) is iFlt(-F);

    private
    digitVal('0') is 0l;
    digitVal('1') is 1l;
    digitVal('2') is 2l;
    digitVal('3') is 3l;
    digitVal('4') is 4l;
    digitVal('5') is 5l;
    digitVal('6') is 6l;
    digitVal('7') is 7l;
    digitVal('8') is 8l;
    digitVal('9') is 9l;

  } in parseNum(Str);

  private
  isDigit('0') is true;
  isDigit('1') is true;
  isDigit('2') is true;
  isDigit('3') is true;
  isDigit('4') is true;
  isDigit('5') is true;
  isDigit('6') is true;
  isDigit('7') is true;
  isDigit('8') is true;
  isDigit('9') is true;
  isDigit(_) default is false;

  private razer(E) is raise E;
}
